﻿using System;
using System.Collections.Generic;
using System.Text;

namespace GArphics
{
    /// <summary>
    /// Operators-enumeration defines the mathematical operator available in the program.
    /// </summary>
    public enum Operators { NULL = 0, MOD, ABS, AND, OR, XOR, SIN, COS, TAN, DIV, MULT, PLUS, MINUS, ACOS, ASIN, ATAN, ATAN2, COSH, EXP, LOG, LOG10, MAX, MIN, POW, SINH, SQRT, TANH };// BW_NOISE, COLOR_NOISE };
    public enum Operands { NULL = 0, VALUE, X, Y };

    /// <summary>
    /// Operator-class defines the calculation of operators.
    /// </summary>
    public static class Operator
    {
        /// <summary>
        /// Tells if the corresponding operator needs second operand or if it can only be used with one operand.
        /// </summary>
        public static bool[] SecondOperandNeeded = new bool[] { false, true, false, true, true, true, false, false, false, true, true, true, true, false, false, false, true, false, false, true, false, true, true, true, false, false, false };

        /// <summary>
        /// In case the operation can't be performed with the provided input-values, the FailValue will be returned.
        /// This is set to 0d by default.
        /// </summary>
        public static double FailValue = 0d;

        /// <summary>
        /// Gets the value resulted by performing the provided operator with the provided parameters.
        /// Some operator only use the value on Value1.
        /// </summary>
        /// <param name="Operator">Operator which should be performed.</param>
        /// <param name="Value1">First operand.</param>
        /// <param name="Value2">Second operand.</param>
        /// <returns>Result when operator is performed.</returns>
        public static double GetValue(Operators Operator, double Value1, double Value2)
        {
            try
            {
                switch (Operator)
                {
                    case Operators.MOD:
                        if (Value2 == 0d)   //Denominator can't be zero.
                            return FailValue;

                        return Value1 % Value2;
                    case Operators.ABS:
                        return Math.Abs(Value1);
                    case Operators.AND:
                        {
                            int iScaledValue1, iScaledValue2;
                            double dMultiplier;

                            Scale(Value1, Value2, out iScaledValue1, out iScaledValue2, out dMultiplier);

                            if (dMultiplier != double.NaN)
                            {
                                return (iScaledValue1 & iScaledValue2) / dMultiplier;
                            }
                            else
                                return FailValue;
                        }
                    case Operators.OR:
                        {
                            int iScaledValue1, iScaledValue2;
                            double dMultiplier;

                            Scale(Value1, Value2, out iScaledValue1, out iScaledValue2, out dMultiplier);

                            if (dMultiplier != double.NaN)
                            {
                                return (iScaledValue1 | iScaledValue2) / dMultiplier;
                            }
                            else
                                return FailValue;
                        }

                    case Operators.XOR:
                        {
                            int iScaledValue1, iScaledValue2;
                            double dMultiplier;

                            Scale(Value1, Value2, out iScaledValue1, out iScaledValue2, out dMultiplier);

                            if (dMultiplier != double.NaN)
                            {
                                return (iScaledValue1 ^ iScaledValue2) / dMultiplier;
                            }
                            else
                                return FailValue;
                        }
                    case Operators.SIN:
                        return Math.Sin(Value1);
                    case Operators.COS:
                        return Math.Cos(Value1);
                    case Operators.TAN:
                        return Math.Tan(Value1);
                    case Operators.DIV:
                        if (Value2 == 0d)   //Denominator can't be zero.
                            return FailValue;
                        return Value1 / Value2;
                    case Operators.MULT:
                        return Value1 * Value2;
                    case Operators.PLUS:
                        return Value1 + Value2;
                    case Operators.MINUS:
                        return Value1 - Value2;
                    case Operators.ACOS:
                        return Math.Acos(Value1);
                    case Operators.ASIN:
                        return Math.Asin(Value1);
                    case Operators.ATAN:
                        return Math.Atan(Value1);
                    case Operators.ATAN2:
                        return Math.Atan2(Value1, Value2);
                    case Operators.COSH:
                        return Math.Cosh(Value1);
                    case Operators.EXP:
                        return Math.Exp(Value1);
                    case Operators.LOG:
                        return Math.Log(Value1, Value2);
                    case Operators.LOG10:
                        return Math.Log(Value1);
                    case Operators.MAX:
                        return Math.Max(Value1, Value2);
                    case Operators.MIN:
                        return Math.Min(Value1, Value2);
                    case Operators.POW:
                        return Math.Pow(Value1, Value2);
                    case Operators.SINH:
                        return Math.Sinh(Value1);
                    case Operators.SQRT:
                        return Math.Sqrt(Value1);
                    case Operators.TANH:
                        return Math.Tanh(Value1);
                }
            }
            catch (OverflowException) { }
            catch (Exception) { }

            return FailValue;
        }

        /// <summary>
        /// Scaling/converting to integers so that the loss of precision is the absolute minimum.
        /// </summary>
        /// <param name="Value1">First operand</param>
        /// <param name="Value2">Second operand</param>
        /// <param name="iScaledValue1">First operand scaled to integer</param>
        /// <param name="iScaledValue2">Second operand scaled to integer</param>
        /// <param name="dMultiplier">Multiplier for scaling back to double</param>
        private static void Scale(double Value1, double Value2, out int iScaledValue1, out int iScaledValue2, out double dMultiplier)
        {
            dMultiplier = Math.Pow(10, 9 - Math.Ceiling(Math.Max(Math.Log10(Math.Abs(Value1)), Math.Log10(Math.Abs(Value2)))));

            double dTempScaled1 = Value1 * dMultiplier;
            double dTempScaled2 = Value2 * dMultiplier;

            if (dTempScaled1 <= int.MaxValue && dTempScaled1 >= int.MinValue &&
                dTempScaled2 <= int.MaxValue && dTempScaled2 >= int.MinValue)
            {
                iScaledValue1 = Convert.ToInt32(dTempScaled1);
                iScaledValue2 = Convert.ToInt32(dTempScaled2);
            }
            else
            {
                iScaledValue1 = 0;
                iScaledValue2 = 0;
                dMultiplier = double.NaN;
            }
        }
    }
}
